<?php

/**
 * 单文件上传类
 * @author yangxin.vip@qq.com
 * @version 1.0
*/
class Upload {

	//默认配置
	private $config=array(
		'maxSize'           =>-1,//文件上传的最大文件大小（以字节为单位）默认为-1 不限大小
		'savePath'          =>'',//文件保存路径，例./Uploads/test/
		'saveRule'          =>'uniqid',//上传文件的保存规则，必须是一个无需任何参数的函数名,默认uniqid
		'hashType'          =>'md5_file',//上传文件的哈希验证方法，默认是md5_file
		'allowExts'         =>'',//允许上传的文件后缀,逗号分割（留空为不限制）
		'allowTypes'        =>'',//允许上传的文件类型,逗号分割（留空为不限制）
		'uploadReplace'     =>true,//存在同名文件是否是覆盖，默认覆盖
	);

    //获取配置
    private function __get($name){
        if(isset($this->config[$name])){
            return $this->config[$name];
        }
        return null;
    }

    //设置配置
    private function __set($name,$value){
        if(isset($this->config[$name])){
            $this->config[$name]=$value;
        }
    }

    //验证配置
    private function __isset($name){
        return isset($this->config[$name]);
    }

	/**
	 * 保存文件
	 * @param array $file 文件列表
	 * @return mixed
	*/
	public function save($files){
		//转换文件数组
		if(($files=$this->dealFiles($files))===false){
			return array('status'=>0,'info'=>'上传文件不能为空！');
		}
		//检测文件错误
		if(($info=$this->checkFiles($files))!==true){
			return array('status'=>0,'info'=>$info);
		}
		//移动文件
		if(($data=$this->moveFiles($files))!==false){
			return array('status'=>1,'info'=>'上传文件成功！','data'=>$data);
		}else{
			return array('status'=>0,'info'=>'上传文件失败！');
		}
	}

	/**
	 * 转换文件数组
	 * @param array $file 文件列表
	 * @return array
	*/
	private function dealFiles($files){
		//重置数据格式
		$result=array();
		if(is_array($files['name'])){
			for($i=0;$i<count($files['name']);$i++){
				$file=array();
				foreach($files as $key=>$value){
					$file[$key]=$value[$i];
				}
				$result[]=$file;
			}
		}else if(is_string($files['name'])){
			$result['only']=$files;
		}
		//添加属性
		foreach($result as $key=>$value){
			$saveRule=$this->saveRule;
			$result[$key]['extension']=strtolower(pathinfo($value['name'],PATHINFO_EXTENSION));
			$result[$key]['savename']=$saveRule().'.'.$result[$key]['extension'];
			$result[$key]['savepath']=$this->savePath;
		}
		return empty($result)?false:$result;
	}

	/**
	 * 上传检测
	 * @param array $files 文件列表
	 * @return mixed
	*/
	private function checkFiles($files) {
		$result=array();
		foreach($files as $key=>$value){
			if(($error=$this->checkError($value['error']))!==true){
				$result[$key]=$error;
			}else if(($error=$this->checkConfig())!==true){
				$result[$key]=$error;
			}else if(($error=$this->checkPath($value['savepath'],$value['savename']))!==true){
				$result[$key]=$error;
			}else if($this->checkSize($value['size'])!==true){
				$error='上传文件大小不符！';
				$result[$key]=$error;
			}else if($this->checkExts($value['extension'])!==true){
				$error='上传文件类型不允许';
				$result[$key]=$error;
			}else if($this->checkType($value['type'])!==true){
				$error='上传文件MIME类型不允许！';
				$result[$key]=$error;
			}else if(is_uploaded_file($value['tmp_name'])!==true){
				$error='非法上传文件！';
				$result[$key]=$error;
			}
		}
		if(!empty($result)){
			$keys=array_keys($result);
			return is_string($keys[0])?$result[$keys[0]]:$result;
		}
		return true;
	}

	/**
	 * 移动文件
	 * @param array $files 文件列表
	 * @return mixed
	*/
	private function moveFiles($files){
		$result=array();
		foreach($files as $key=>$value){
			if(move_uploaded_file($value['tmp_name'],$value['savepath'].$value['savename'])){
				$data=array(
					'name'=>$value['name'],
					'type'=>$value['type'],
					'size'=>$value['size'],
					'extension'=>$value['extension'],
					'savename'=>$value['savename'],
					'savepath'=>$value['savepath'],
					'hash'=>$this->config['hashType']($value['savepath'].$value['savename']),
				);
				is_string($key)?$result=$data:$result[]=$data;
			}
		}
		return empty($result)?false:$result;
	}

	/**
	 * 检测配置
	 * @return mixed
	*/
	private function checkConfig(){
		$config=$this->config;
		if(empty($config['savePath'])){
			return '文件保存路径不能为空！';
		}else if(empty($config['saveRule'])){
			return '文件的保存规则不能为空！';
		}else if(empty($config['hashType'])){
			return '上传文件的哈希验证方法不能为空！';
		}else{
			return true;
		}
	}

	/**
	 * 检测路径
	 * @param array $savePath 文件路径
	 * @param array $saveName 文件名称
	 * @return mixed
	*/
	private function checkPath($savePath,$saveName){
		if(!is_dir($savePath)){
			if(!mkdir($savePath)){
				return '上传目录'.$savePath.'不存在';
			}
		}else if(!is_writeable($savePath)){
			return '上传目录'.$savePath.'不可写';
		}else if(!$this->config['uploadReplace']&&is_file($savePath.$saveName)){
			return '文件已存在！';
		}
		return true;
	}

	/**
	 * 检测文件大小
	 * @param array $size 文件大小
	 * @return bool
	*/
	private function checkSize($size){
		if($this->config['maxSize']>=0){
			return $size<=$this->config['maxSize']?true:false;
		}else{
			return true;
		}
	}

	/**
	 * 检测文件后缀类型
	 * @param array $extension 文件后缀
	 * @return mixed
	*/
	private function checkExts($extension){
		if(!empty($this->config['allowExts'])){
			$allowTypes=explode(',',trim(strtolower($this->config['allowExts']),','));
			return in_array($extension,$allowTypes)?true:false;
		}else{
			return true;
		}
	}

	/**
	 * 检测文件Mime类型
	 * @param array $type 文件类型
	 * @return bool
	*/
	private function checkType($type){
		if(!empty($this->config['allowTypes'])){
			$allowTypes=explode(',',trim(strtolower($this->config['allowTypes']),','));
			return in_array($extension,$allowTypes)?true:false;
		}else{
			return true;
		}
	}

	/**
	 * 错误提示
	 * @param array $errorNo 错误编号
	 * @return string
	*/
	private function checkError($errorNo) {
		 switch($errorNo) {
			case 0:
				return true;
				break;
			case 1:
				return '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值';
				break;
			case 2:
				return '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值';
				break;
			case 3:
				return '文件只有部分被上传';
				break;
			case 4:
				return '没有文件被上传';
				break;
			case 6:
				return '找不到临时文件夹';
				break;
			case 7:
				return '文件写入失败';
				break;
			default:
				return '未知上传错误！';
		}
	}

}

?>